#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#include <tokenhelper.h>

#include <string>

// stream
static FILE                    *_fp;

static std::string              _fn;

static int                      _lineno;

static std::string              _tk_buf;

static int
_fgetc()
{
    static int r = 0;

    if (_fp) {
        int c = fgetc(_fp);

        switch (c) {
        case '\r':
            ++_lineno;
            r = 1;
            break;

        case '\n':
            if (!r)
                ++_lineno;
            else
                r = 0;
            break;

        default:
            r = 0;
            break;
        }

        return c;
    }

    return EOF;
}

// skip comment
static int
_skip_comment()
{
    if (_fp) {
        int c;
        int go = 1;
        while (go && (c = _fgetc()) != EOF) {
            switch (c) {
            case '\r':
                c = _fgetc();
                if (c != EOF) {
                    if (c != '\n')
                        ungetc(c, _fp);
                }
                go = 0;
                break;

            case '\n':
                go = 0;
                break;
            }
        }
    }
    return 0;
}

// string
static int
_skip_string()
{
    if (_fp) {
        int c;
        int go = 1;
        int bs = 0;
        while (go && (c = _fgetc()) != EOF) {
            if (c == '"') {
                if (!bs)
                    go = 0;
            }

            if (bs)
                bs = 0;
            else if (c == '\\')
                bs = 1;
        }
    }
    return 0;
}

static int
_get_token()
{
    int c;
    int bs;

    if (_fp == NULL)
        return 0;

    bs = 0;

    while ((c = _fgetc()) != EOF) {
        switch (c) {
        default:
            _tk_buf.push_back(c);
            bs = 0;
            break;

        case ' ':
        case '\t':
        case '\r':
        case '\n':
        case '\f':
            return 0;
            break;

        case '(':
        case ')':
        case '[':
        case ']':
            if (!bs) {
                ungetc(c, _fp);
                return 0;
            }
            else
                bs = 0;
            break;

        case '\\':
            if (!bs)
                bs = 1;
            else
                bs = 0;
            break;
        }
    }
    return 0;
}

static int
_next_token(int *lineno)
{
    if (!_fp)
        return 0;

    _tk_buf.clear();

    int c;
    for (;;) {
        c = _fgetc();

        switch (c) {
        default:
            _tk_buf.push_back(c);
            if (lineno)
                *lineno = _lineno;
            _get_token();
            return 1;
            break;

        case '(':
        case ')':
        case '[':
        case ']':
            _tk_buf.push_back(c);
            return 1;

        case ' ':
        case '\t':
        case '\r':
        case '\n':
        case '\f':
            break;

        case '"':
            _skip_string();
            break;

        case ';':
            _skip_comment();
            break;

        case EOF:
            return 0;
            break;
        }
    }

    // never go here
    return 0;
}

// interface
int tk_open_file(const char *fn)
{
    if (_fp)
        fclose(_fp);

    _fp = fopen(fn, "r");

    _lineno = 1;

    _fn = fn;

    return _fp ? 0 : -1;
}

int tk_close_file()
{
    if (_fp) {
        fclose(_fp);
        _fp = NULL;
    }

    return 0;
}

#define HANDLE_DEPTH()                          \
    do {                                        \
        switch (_tk_buf[0]) {                   \
        case '(':                               \
            ++depth;                            \
            break;                              \
        case ')':                               \
            --depth;                            \
            break;                              \
        }                                       \
    } while (0)

int tk_fetch_next(int *lineno, std::string& token)
{
    int depth;
    int type;

    if (!_next_token(NULL))
        return TT_NONE;

    // skip all [ ] ' in global scope
    while (strchr("[]'", _tk_buf[0])) {
        if (!_next_token(NULL))
            return TT_NONE;
        else
            break;
    }

    if (_tk_buf[0] != '(') {
        fprintf(stderr, "war: the first token is not ( at line %d, fn %s\n",
                _lineno, _fn.c_str());
        return TT_NONE;
    }

    depth = 1;

    if (!_next_token(NULL))
        return TT_NONE;

    // type
    if (_tk_buf == "defun" || _tk_buf == "defun*"
        || _tk_buf == "defsubst" || _tk_buf == "defmethod")
        type = TT_FUNC;
    else if (_tk_buf == "defvar" || _tk_buf == "defcustom")
        type = TT_VAR;
    else if (_tk_buf == "defmacro")
        type = TT_MACRO;
    else if (_tk_buf == "defconst")
        type = TT_CONST;
    else {
        type = TT_UNKNOW;
    }

    // token
    if (!_next_token(lineno))
        return TT_NONE;

    token = _tk_buf;

    // ignored the others
    for (;;) {
        HANDLE_DEPTH();

        if (depth == 0)
            break;

        if (!_next_token(NULL))
            break;
    }

    return type;
}
